/* Simple HTTP Server Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdlib.h>
#include <stdint.h>

#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_log.h>
#include <esp_system.h>
#include <nvs_flash.h>
#include <sys/param.h>
#include "nvs_flash.h"
#include "esp_netif.h"
#include "driver/gpio.h"
#include "cJSON.h"

#include <esp_http_server.h>

#include "app_wifi.h"
#include "url_handlers.h"

// Set number of outputs
#define NUM_OUTPUTS  4
// Html return Parameters
static const char *param_output = "output";
static const char *param_state = "state";

// Array with outputs you want to control
int outputGPIOs[NUM_OUTPUTS] = {2, 4, 12, 14};

/* A simple example that demonstrates how to create GET and POST
 * handlers for the web server.
 */

static const char *TAG = "example_main";    
static httpd_handle_t server = NULL;

//Stupid li'l helper function that returns the value of a hex char.
static int httpdHexVal(char c)
{
    if (c >= '0' && c <= '9') {
        return c - '0';
    }

    if (c >= 'A' && c <= 'F') {
        return c - 'A' + 10;
    }

    if (c >= 'a' && c <= 'f') {
        return c - 'a' + 10;
    }

    return 0;
}

//Decode a percent-encoded value.
//Takes the valLen bytes stored in val, and converts it into at most retLen bytes that
//are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also
//zero-terminates the ret buffer.
int httpdUrlDecode(char* val, int valLen, char* ret, int retLen)
{
    int s = 0, d = 0;
    int esced = 0, escVal = 0;

    while (s < valLen && d < retLen) {
        if (esced == 1)  {
            escVal = httpdHexVal(val[s]) << 4;
            esced = 2;
        } else if (esced == 2) {
            escVal += httpdHexVal(val[s]);
            ret[d++] = escVal;
            esced = 0;
        } else if (val[s] == '%') {
            esced = 1;
        } else if (val[s] == '+') {
            ret[d++] = ' ';
        } else {
            ret[d++] = val[s];
        }

        s++;
    }

    if (d < retLen) {
        ret[d] = 0;
    }

    return d;
}

//Find a specific arg in a string(/update?output=4&state=1) of get- or post-data.
//Line is the string of post/get-data, arg is the name of the value to find. The
//zero-terminated result is written in buff, with at most buffLen bytes used. The
//function returns the length of the result, or -1 if the value wasn't found. The
//returned string will be urldecoded already.
int httpd_find_arg(const char* line, const char* arg, char* buff, int buffLen)
{
    char* p, *e;
    bool first_in = true;

    if (line == NULL) {
        return -1;
    }

    p = line;

    while (p != NULL && *p != '\n' && *p != '\r' && *p != 0) {
        // printf("findArg: %s\n", p);
        if (strncmp(p, arg, strlen(arg)) == 0 && p[strlen(arg)] == '=') {
            p += strlen(arg) + 1; //move p to start of value
            e = (char*)strstr(p, "&");

            if (e == NULL) {
                e = p + strlen(p);
            }

            // printf("findArg: val %s len %d\n", p, (e-p));
            return httpdUrlDecode(p, (e - p), buff, buffLen);
        }

        if(first_in) {
            p = (char*)strstr(p, "?");
            first_in = false;
        } else {
            p = (char*)strstr(p, "&");
        }
        
        if (p != NULL) {
            p += 1;
        }
    }

    printf("Finding %s in %s: Not found :/\n", arg, line);
    return -1; //not found
}

/* Handler to redirect incoming GET request for /index.html to /
 * This can be overridden by uploading file with same name */
static void redirect_index_html(httpd_req_t *req)
{
    httpd_resp_set_status(req, "307 Temporary Redirect");
    httpd_resp_set_hdr(req, "Location", "/");
    httpd_resp_send(req, NULL, 0);  // Response body can be empty
}

#define TEMP_STR_MAX_LEN (64)
// Return JSON with Current Output States
char *getOutputStates() 
{
    char temp_str1[TEMP_STR_MAX_LEN] = {0};
    char temp_str2[TEMP_STR_MAX_LEN] = {0};
    char temp_str3[TEMP_STR_MAX_LEN] = {0};
    int body_len1 = 0;
    int body_len2 = 0;

    cJSON* root = cJSON_CreateObject();
    cJSON *gpio_array = cJSON_CreateArray();
    cJSON_AddItemToObject(root, "gpios", gpio_array);

    for (int i =0; i<NUM_OUTPUTS; i++) {
        memset(temp_str1, '\0', TEMP_STR_MAX_LEN * sizeof(char));
        memset(temp_str2, '\0', TEMP_STR_MAX_LEN * sizeof(char));
        memset(temp_str3, '\0', TEMP_STR_MAX_LEN * sizeof(char));
        body_len1 = 0;
        body_len2 = 0;
        cJSON *gpio_item = cJSON_CreateObject();
        cJSON_AddItemToArray(gpio_array, gpio_item);
        body_len1 += sprintf((char *)temp_str1 + body_len1, "output");
        body_len2 += sprintf((char *)temp_str2 + body_len2, "state");

        itoa(outputGPIOs[i], temp_str3, 10);
        cJSON_AddStringToObject(gpio_item, temp_str1, temp_str3);

        itoa(gpio_get_level(outputGPIOs[i]), temp_str3, 10);
        cJSON_AddStringToObject(gpio_item, temp_str2, temp_str3);
    }
    char* printed_json = cJSON_Print(root);
    cJSON_Delete(root);
    return printed_json;
}

static esp_err_t output_states_get_handler(httpd_req_t *req)
{
    char* printed_json = getOutputStates();
    ESP_LOGI(TAG, "get system info:%s\n", printed_json);
    httpd_resp_set_type(req, "application/json");

    httpd_resp_sendstr(req, printed_json);
    free((void*) printed_json);
    return ESP_OK;
}

static esp_err_t update_state_get_handler(httpd_req_t *req)
{
    char temp_str[64] = {0};
    int str_len = 0;
    int gpio_num = -1;
    int gpio_state = -1;

    ESP_LOGI(TAG, "req:%s", req->uri);

    str_len = httpd_find_arg(req->uri, param_output, temp_str, sizeof(temp_str));
    if ((str_len == -1) || (strlen((char *)temp_str) == 0)) {
        ESP_LOGE(TAG, "no gpio output message find");
    } else {
        gpio_num = atoi(temp_str);
        ESP_LOGI(TAG, "updates:gpio_num=%d", gpio_num);
    }

    memset(temp_str, '\0', 64 * sizeof(char));

    if(gpio_num != -1) {
        str_len = httpd_find_arg(req->uri, param_state, temp_str, sizeof(temp_str));
        if ((str_len == -1) || (strlen((char *)temp_str) == 0)) {
            ESP_LOGE(TAG, "no status find");
        } else {
            gpio_state = atoi(temp_str);
            gpio_set_level(gpio_num, gpio_state);
            redirect_index_html(req); // You must reload the webpage, otherwise there may be a significant delay in the next operation
            ESP_LOGI(TAG, "updates:gpio[%d]_status=%d", gpio_num, gpio_state);
        }
    }

    return ESP_OK;
}

httpd_uri_t peri_httpd_uri_array[] = {
    {"/states", HTTP_GET, output_states_get_handler, NULL},
    {"/update", HTTP_GET, update_state_get_handler, NULL},
};

static httpd_handle_t start_webserver(void)
{
    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.lru_purge_enable = true;

    // Start the httpd server
    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
    if (httpd_start(&server, &config) == ESP_OK) {
        // Set URI handlers
        ESP_LOGI(TAG, "Registering URI handlers");
        for (int i = 0; i < sizeof(httpd_uri_array) / sizeof(httpd_uri_t); i++) {
            if (httpd_register_uri_handler(server, &httpd_uri_array[i]) != ESP_OK) {
                ESP_LOGE(TAG, "httpd register uri_array[%d] fail", i);
            }
        }

        for (int i = 0; i < sizeof(peri_httpd_uri_array) / sizeof(httpd_uri_t); i++) {
            if (httpd_register_uri_handler(server, &peri_httpd_uri_array[i]) != ESP_OK) {
                ESP_LOGE(TAG, "httpd register peri_uri_array[%d] fail", i);
            }
        }

        ESP_LOGI(TAG, "Success starting server!");
        return server;
    }

    ESP_LOGI(TAG, "Error starting server!");
    return NULL;
}

static void configure_led(void)
{
    ESP_LOGI(TAG, "Example configured to blink GPIO LED!");
    for(int i=0; i< (sizeof(outputGPIOs)/sizeof(int));i++) {
        gpio_reset_pin(outputGPIOs[i]);
        /* Set the GPIO as a push/pull output */
        gpio_set_direction(outputGPIOs[i], GPIO_MODE_OUTPUT);
    }
}

static void stop_webserver(httpd_handle_t server)
{
    // Stop the httpd server
    httpd_stop(server);
}

void app_main(void)
{
    /* Configure the peripheral according to the LED type */
    configure_led();
    getOutputStates();

    // connect wifi
    app_wifi_main();

    /* Start the server for the first time */
    server = start_webserver();
}
